﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using System.Web.UI;
using Aga.Psad.Mvc.Models.UserAdministration;
using System.Threading;
using Aga.Psad.Mvc.Helpers;
using Aga.Psad.Backend.Repositories;
using Aga.Psad.Backend.Entities;

namespace Aga.Psad.Mvc.Controllers
{

    [HandleError]
    public class AccountController : ControllerBase
    {

        // This constructor is used by the MVC framework to instantiate the controller using
        // the default forms authentication and membership providers.

        public AccountController()
            : this(null, null)
        {
        }

        // This constructor is not used by the MVC framework but is instead provided for ease
        // of unit testing this type. See the comments at the end of this file for more
        // information.
        public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
        {
            FormsAuth = formsAuth ?? new FormsAuthenticationService();
            MembershipService = service ?? new AccountMembershipService();
        }

        public IFormsAuthentication FormsAuth
        {
            get;
            private set;
        }

        public IMembershipService MembershipService
        {
            get;
            private set;
        }

        public ActionResult LogOn()
        {
            if (Request.RawUrl.ToUpper().Contains("CMC"))
                return View("CMCLogOn");

            return View();
        }

        public ActionResult CMCLogOn()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as Controller.Redirect()")]
        public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
        {
            if (!ValidateLogOn(userName, password))
            {
                return View();
            }

            FormsAuth.SignIn(userName, rememberMe);

            AuditoriaRepository audit = new AuditoriaRepository();
            audit.Auditar(userName, AccionAuditoria.Ingreso);

            // chequeo el cliente asociado con el usuario
            ClienteRepository clienteRepository = new ClienteRepository();
            Cliente cliente = clienteRepository.GetByUserName(userName).FirstOrDefault();

            // si tiene un cliente asociado, le tomo el tipo
            if (cliente != null)
            {
                SessionHelper.TipoDeUsuario = cliente.Tipo;
            }
            else
            {
                ClienteDeClienteRepository clienteDeClienteRepository = new ClienteDeClienteRepository();
                ClienteDeCliente clienteDeCliente = clienteDeClienteRepository.GetByUserName(userName).FirstOrDefault();

                if (clienteDeCliente != null)
                {
                    SessionHelper.TipoDeUsuario = clienteDeCliente.Cliente.Tipo;
                }
            }

            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
            Justification = "Needs to take same parameter type as Controller.Redirect()")]
        public ActionResult CMCLogOn(string userName, string password, bool rememberMe, string returnUrl)
        {

            if (!ValidateLogOn(userName, password))
            {
                return View();
            }

            FormsAuth.SignIn(userName, rememberMe);

            AuditoriaRepository audit = new AuditoriaRepository();
            audit.Auditar(userName, AccionAuditoria.Ingreso);

            if (!String.IsNullOrEmpty(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("GuardaDocumental", "CMC");
            }
        }

        public ActionResult LogOff()
        {

            FormsAuth.SignOut();

            return RedirectToAction("Index", "Home");
        }

        public ActionResult CMCLogOff()
        {

            FormsAuth.SignOut();

            return RedirectToAction("CMCLogOn");
        }

        [Authorize]
        public ActionResult Register()
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View();
        }

        [Authorize]
		[AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Register(string userName, string email, string password, string confirmPassword)
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            if (ValidateRegistration(userName, email, password, confirmPassword))
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = MembershipService.CreateUser(userName, password, email);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    //FormsAuth.SignIn(userName, false /* createPersistentCookie */);
                    return RedirectToAction("ViewUser", "UserAdministration", new { id = userName});
                }
                else
                {
                    ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

        [Authorize]
        public ActionResult Details()
        {
            MembershipUser user = MembershipService.GetUser(Thread.CurrentPrincipal.Identity.Name);
            var model = new User { UserName = user.UserName, Email = user.Email };

            return View(model);
        }

        [Authorize]
        public ActionResult CMCDetails()
        {
            MembershipUser user = MembershipService.GetUser(Thread.CurrentPrincipal.Identity.Name);
            var model = new User { UserName = user.UserName, Email = user.Email };

            return View(model);
        }

        [Authorize]
        public ActionResult ChangePassword()
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View();
        }

        [Authorize]
        public ActionResult CMCChangePassword()
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View();
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Exceptions result in password not being changed.")]
        public ActionResult ChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword))
            {
                return View();
            }

            try
            {
                if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword))
                {
                    return RedirectToAction("ChangePasswordSuccess");
                }
                else
                {
                    ModelState.AddModelError("_FORM", "Su actual contraseña es incorrecta o la nueva es inválida.");
                    return View();
                }
            }
            catch
            {
                ModelState.AddModelError("_FORM", "Su actual contraseña es incorrecta o la nueva es inválida.");
                return View();
            }
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Exceptions result in password not being changed.")]
        public ActionResult CMCChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            if (!ValidateChangePassword(currentPassword, newPassword, confirmPassword))
            {
                return View();
            }

            try
            {
                if (MembershipService.ChangePassword(User.Identity.Name, currentPassword, newPassword))
                {
                    return RedirectToAction("CMCChangePasswordSuccess");
                }
                else
                {
                    ModelState.AddModelError("_FORM", "Su actual contraseña es incorrecta o la nueva es inválida.");
                    return View();
                }
            }
            catch
            {
                ModelState.AddModelError("_FORM", "Su actual contraseña es incorrecta o la nueva es inválida.");
                return View();
            }
        }

        public ActionResult ChangePasswordSuccess()
        {

            return View();
        }

        public ActionResult CMCChangePasswordSuccess()
        {
            return View();
        }

        public ActionResult RecoverPassword()
        {
            return View();
        }

        public ActionResult CMCRecoverPassword()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult RecoverPassword(string username, string email)
        {
            try
            {
                if (String.IsNullOrEmpty(username))
                    ModelState.AddModelError("username", "El nombre de usuario no puede quedar vacío.");
                if (String.IsNullOrEmpty(email))
                    ModelState.AddModelError("email", "El e-mail no puede quedar vacío.");

                if (!ModelState.IsValid)
                    return View();

                MembershipUser user = MembershipService.GetUser(username);

                if (user == null)
                {
                    ModelState.AddModelError("username", "No se encontró el usuario ingresado. Verifíque los datos y vuelva a intentarlo.");
                    return View();
                }

                if (!user.Email.Equals(email))
                {
                    ModelState.AddModelError("email", "El e-mail ingresado no es válido. Verifíque los datos y vuelva a intentarlo.");
                    return View();
                }

                var usersService = new MvcMembership.AspNetMembershipProviderWrapper(Membership.Provider);
                var rolesService = new MvcMembership.AspNetRoleProviderWrapper(Roles.Provider);

                // Must enable password retrieval from Web.config (enablePasswordRetrieval=true)
                var pass = user.GetPassword();
                EmailHelper.SendResetPassEmail(username, pass, email);

                return View("PasswordRecovered");

            }
            catch (Exception ex)
            {
                return ShowError(ex);
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult CMCRecoverPassword(string username, string email)
        {
            try
            {
                if (String.IsNullOrEmpty(username))
                    ModelState.AddModelError("username", "El nombre de usuario no puede quedar vacío.");
                if (String.IsNullOrEmpty(email))
                    ModelState.AddModelError("email", "El e-mail no puede quedar vacío.");

                if (!ModelState.IsValid)
                    return View();

                MembershipUser user = MembershipService.GetUser(username);

                if (user == null)
                {
                    ModelState.AddModelError("username", "No se encontró el usuario ingresado. Verifíque los datos y vuelva a intentarlo.");
                    return View();
                }

                if (!user.Email.Equals(email))
                {
                    ModelState.AddModelError("email", "El e-mail ingresado no es válido. Verifíque los datos y vuelva a intentarlo.");
                    return View();
                }

                var usersService = new MvcMembership.AspNetMembershipProviderWrapper(Membership.Provider);
                var rolesService = new MvcMembership.AspNetRoleProviderWrapper(Roles.Provider);

                // Must enable password retrieval from Web.config (enablePasswordRetrieval=true)
                var pass = user.GetPassword();
                EmailHelper.SendResetPassEmail(username, pass, email);

                return View("PasswordRecovered");

            }
            catch (Exception ex)
            {
                return ShowError(ex);
            }
        }

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity is WindowsIdentity)
            {
                throw new InvalidOperationException("Windows Authentication no está soportada.");
            }
        }

        #region Validation Methods

        private bool ValidateChangePassword(string currentPassword, string newPassword, string confirmPassword)
        {
            if (String.IsNullOrEmpty(currentPassword))
            {
                ModelState.AddModelError("currentPassword", "Debe ingresar su actual contraseña.");
            }
            if (newPassword == null || newPassword.Length < MembershipService.MinPasswordLength)
            {
                ModelState.AddModelError("newPassword",
                    String.Format(CultureInfo.CurrentCulture,
                         "Debe ingresar una contraseña de {0} caracteres o más.",
                         MembershipService.MinPasswordLength));
            }

            if (!String.Equals(newPassword, confirmPassword, StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "La nueva contraseña y la confirmación no concuerdan.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateLogOn(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "Debe ingresar un nombre de usuario.");
            }
            if (String.IsNullOrEmpty(password))
            {
                ModelState.AddModelError("password", "Debe ingresar una contraseña.");
            }
            if (!MembershipService.ValidateUser(userName, password))
            {
                ModelState.AddModelError("_FORM", "El nombre de usuario o la contraseña ingresados son incorrectos.");
            }

            return ModelState.IsValid;
        }

        private bool ValidateRegistration(string userName, string email, string password, string confirmPassword)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("username", "Debe ingresar un nombre de usuario.");
            }
            if (String.IsNullOrEmpty(email))
            {
                ModelState.AddModelError("email", "Debe ingresar un e-mail.");
            }
            if (password == null || password.Length < MembershipService.MinPasswordLength)
            {
                ModelState.AddModelError("password",
                    String.Format(CultureInfo.CurrentCulture,
                         "Debe ingresar una contraseña de {0} caracteres o más.",
                         MembershipService.MinPasswordLength));
            }
            if (!String.Equals(password, confirmPassword, StringComparison.Ordinal))
            {
                ModelState.AddModelError("_FORM", "La nueva contraseña y la confirmación no concuerdan.");
            }
            return ModelState.IsValid;
        }

        private static string ErrorCodeToString(MembershipCreateStatus createStatus)
        {
            // See http://msdn.microsoft.com/en-us/library/system.web.security.membershipcreatestatus.aspx for
            // a full list of status codes.
            switch (createStatus)
            {
                case MembershipCreateStatus.DuplicateUserName:
                    return "El usuario ya existe. Por favor, ingrese otro nombre de usuario.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "Ya existe un usuario para esa cuenta de e-mail. Por favor, ingrese otra cuenta de e-mail.";

                case MembershipCreateStatus.InvalidPassword:
                    return "La contraseña ingresada en inválida. Por favor, ingrese una contraseña válida.";

                case MembershipCreateStatus.InvalidEmail:
                    return "El e-mail ingresado es inválido. Por favor, verifíque los datos y vuelva a intentarlo.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "La respuesta de recuperación de contraseña es incorrecta. Por favor, verifíque los datos y vuelva a intentarlo.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "La pregunta de recuperación de contraseña es incorrecta. Por favor, verifíque los datos y vuelva a intentarlo.";

                case MembershipCreateStatus.InvalidUserName:
                    return "El nombre de usuario es incorrecto. Por favor, verifíque los datos y vuelva a intentarlo.";

                case MembershipCreateStatus.ProviderError:
                    return "El servidor de autenticación devolvió un error. Por favor, verifíque los datos y vuelva a intentarlo. Si el problema persiste, coctacte al administrador del sistema.";

                case MembershipCreateStatus.UserRejected:
                    return "La solicitud de creación de usuario ha sido rechazada. Por favor, verifíque los datos y vuelva a intentarlo. Si el problema persiste, coctacte al administrador del sistema.";

                default:
                    return "Ha ocurrido un error desconocido. Por favor, verifíque los datos y vuelva a intentarlo. Si el problema persiste, coctacte al administrador del sistema.";
            }
        }
        #endregion
    }

    // The FormsAuthentication type is sealed and contains static members, so it is difficult to
    // unit test code that calls its members. The interface and helper class below demonstrate
    // how to create an abstract wrapper around such a type in order to make the AccountController
    // code unit testable.

    public interface IFormsAuthentication
    {
        void SignIn(string userName, bool createPersistentCookie);
        void SignOut();
    }

    public class FormsAuthenticationService : IFormsAuthentication
    {
        public void SignIn(string userName, bool createPersistentCookie)
        {
            FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
        }
        public void SignOut()
        {
            FormsAuthentication.SignOut();
        }
    }

    public interface IMembershipService
    {
        int MinPasswordLength { get; }

        bool ValidateUser(string userName, string password);
        MembershipCreateStatus CreateUser(string userName, string password, string email);
        bool ChangePassword(string userName, string oldPassword, string newPassword);
        MembershipUser GetUser(string username);
    }

    public class AccountMembershipService : IMembershipService
    {
        private MembershipProvider _provider;

        public AccountMembershipService()
            : this(null)
        {
        }

        public AccountMembershipService(MembershipProvider provider)
        {
            _provider = provider ?? Membership.Provider;
        }

        public int MinPasswordLength
        {
            get
            {
                return _provider.MinRequiredPasswordLength;
            }
        }

        public bool ValidateUser(string userName, string password)
        {
            return _provider.ValidateUser(userName, password);
        }

        public MembershipCreateStatus CreateUser(string userName, string password, string email)
        {
            MembershipCreateStatus status;
            _provider.CreateUser(userName, password, email, null, null, true, null, out status);
            return status;
        }

        public bool ChangePassword(string userName, string oldPassword, string newPassword)
        {
            MembershipUser currentUser = _provider.GetUser(userName, true /* userIsOnline */);
            return currentUser.ChangePassword(oldPassword, newPassword);
        }

        public MembershipUser GetUser(string username)
        {
            var user = _provider.GetUser(username, false);
            return user;
        }
    }
}
